/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.datalogging.web.controller;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.openmrs.Encounter;
import org.openmrs.Obs;
import org.openmrs.api.context.Context;
import org.openmrs.module.datalogging.filter.FilterUtils;
import org.openmrs.module.datalogging.handler.variable.LogicVariableHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletResponse;
import java.beans.PropertyDescriptor;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 *
 */
@Controller
@RequestMapping("/module/datalogging/valueFinder")
public class ValueFinderController {
	
	private static final Set<String> excludedProperty;
	
	private static final Map<String, String> baseClasses;
	
	static {
		excludedProperty = new HashSet<String>();
		excludedProperty.add("class");
		
		baseClasses = new HashMap<String, String>();
		baseClasses.put("encounter", Encounter.class.getName());
		baseClasses.put("obs", Obs.class.getName());
	}
	
	/** Logger for this class and subclasses */
	private static final Log log = LogFactory.getLog(ValueFinderController.class);
	
	@RequestMapping(method = RequestMethod.GET)
	public void findValue(@RequestParam("valueExpression") String valueExpression,
	                      @RequestParam("searchTerm") String searchTerm, @RequestParam("baseClass") String baseClass,
	                      HttpServletResponse response) throws Exception {
		if (Context.isAuthenticated()) {
			
			response.setContentType("application/json");
			
			if (log.isDebugEnabled())
				log.debug("Processing: " + baseClass + " and " + valueExpression + " ...");
			
			OutputStream stream = response.getOutputStream();
			
			JsonFactory f = new JsonFactory();
			JsonGenerator g = f.createJsonGenerator(stream, JsonEncoding.UTF8);
			g.useDefaultPrettyPrinter();
			
			g.writeStartObject();
			g.writeArrayFieldStart("elements");
			
			if (baseClass.equals(FilterUtils.RULE_STRING)) {
				// TODO: hack for rule type
				// need to think further about this rule element
				g.writeStartObject();
				g.writeStringField("property", LogicVariableHandler.RULE_NAME);
				g.writeStringField("classname", "Token Name");
				g.writeEndObject();
				g.writeStartObject();
				g.writeStringField("property", LogicVariableHandler.RULE_OUTPUT);
				g.writeStringField("classname", "Result Of Rule Execution");
				g.writeEndObject();
			} else {
				Class<?> classObject = Class.forName(baseClasses.get(baseClass));
				PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(classObject);
				
				String[] properties = StringUtils.split(valueExpression, FilterUtils.PROPERTY_SEPARATOR);
				for (String property : properties) {
					for (PropertyDescriptor propertyDescriptor : descriptors)
						if (property.equals(propertyDescriptor.getName()))
							classObject = propertyDescriptor.getPropertyType();
					descriptors = PropertyUtils.getPropertyDescriptors(classObject);
				}
				
				for (PropertyDescriptor descriptor : descriptors) {
					// skip any property that's in the excluded
					if (excludedProperty.contains(descriptor.getName()))
						continue;
					
					// skip property that is not readable
					if (descriptor.getReadMethod() == null)
						continue;
					
					// filter term that match the search term or display all of them when the search term is empty
					if (StringUtils.isBlank(searchTerm) || StringUtils.containsIgnoreCase(descriptor.getName(), searchTerm)) {
						g.writeStartObject();
						g.writeStringField("property", descriptor.getName());
						// for primitive type, this will return null
						String classname = "Primitive Type";
						Class<?> propertyClass = descriptor.getPropertyType();
						if (propertyClass != null)
							classname = propertyClass.getName();
						g.writeStringField("classname", classname);
						g.writeEndObject();
					}
				}
			}
			g.writeEndArray();
			g.writeEndObject();
			
			g.close();
		}
	}
}
